﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;
using System.Windows.Forms;

using AdaptationProject.Properties;
using SharedTypes;

namespace AdaptationProject.Utils
{
    /// <summary>
    /// Класс предназначен для загрузки динамических библиотек, содержащих
    /// алгоритм исполнительного файла или подбора параметров.
    /// </summary>
    public static class AssemblyLoader
    {
        /// <summary>
        /// Фильтр для поиска библиотек.
        /// </summary>
        private const string DllSearchPattern = "*.dll";

        /// <summary>
        /// Фильтр для поиска XML файлов.
        /// </summary>
        private const string XmlSearchPattern = "*.xml";

        /// <summary>
        /// Путь к папке с алгоритмами сжитя по умолчанию.
        /// </summary>
        private static readonly string algPath = MySettings.AlgorithmsCachePath;

        /// <summary>
        /// Путь к папке с методами поиска параметров по умолчанию.
        /// </summary>
        private static readonly string mtdPath = MySettings.MethodsCachePath;

        /// <summary>
        /// Путь к папке с отчётами по умолчанию.
        /// </summary>
        private static readonly string repPath = MySettings.ReportsStoragePath;

        /// <summary>
        /// Проверяет наличи системных папок и создаёт их, в случае отсутствия.
        /// </summary>
        public static void StartupChecker()
        {
            if (!Directory.Exists(algPath))
            {
                Directory.CreateDirectory(algPath);
            }

            if (!Directory.Exists(mtdPath))
            {
                Directory.CreateDirectory(mtdPath);
            }

            if (!Directory.Exists(repPath))
            {
                Directory.CreateDirectory(repPath);
            }
        }

        /// <summary>
        /// Загружает алгоритмы исполнительного файла, находящиеся в соответствующей папке.
        /// </summary>
        public static void LoadAlgorithms(IXMLParser currentDescr)
        {
            try
            {
                string[] files = Directory.GetFiles(algPath, XmlSearchPattern);
                if (files.Length > 0)
                {
                    foreach (string fileName in files)
                    {
                        LoadAlgorithm(fileName, currentDescr);
                    }
                }
                else
                {
                    throw new Exception(Resources.ASMLoader_NoDllErrorText);
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, Resources.ASMLoader_ErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        /// <summary>
        /// Загружает методы поиска параметров, находящиеся в соответствующей папке.
        /// </summary>
        public static void LoadMethods()
        {
            try
            {
               string[] files = Directory.GetFiles(mtdPath, DllSearchPattern);
               if (files.Length > 0)
               {
                   foreach (string fileName in files)
                   {
                       LoadMethod(fileName);
                   }
               }
               else
               {
                   throw new Exception(Resources.ASMLoader_NoDllErrorText + ": не найдено ни одного метода адаптации");
               }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, Resources.ASMLoader_ErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Загружает метод адаптации параметров.
        /// </summary>
        /// <param name="asmName">Путь к библиотеке, содержащей метод.</param>
        private static void LoadMethod(string asmName)
        {
            Assembly theMethod = null;

            try
            {
                theMethod = Assembly.LoadFrom(asmName);


                var classTypes = from t in theMethod.GetTypes()
                                 where t.IsClass && (t.GetInterface(typeof (IAdoptationMethod).FullName) != null)
                                 select t;

                foreach (Type classType in classTypes)
                {
                    string methodName = string.Empty;
                    KeyValuePair<string, IAdoptationMethod> value;

                    methodName = GetNameFromAtribute(classType);
                    IAdoptationMethod toAdd = (IAdoptationMethod) theMethod.CreateInstance(classType.FullName, true);

                    value = new KeyValuePair<string, IAdoptationMethod>(methodName, toAdd);
                    ResContainer.AddMethod(value, GetAvailableParameters(classType));
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
                return;
            }
        }

        /// <summary>
        /// Загружает алгоритм исполнительного файла.
        /// </summary>
        /// <param name="fileName">Путь к XML файлу, содержащему
        /// описание алгоритма исполнительного файла согласно внутреннему стандарту.</param>
        private static void LoadAlgorithm(string fileName, IXMLParser currentDescr)
        {
            currentDescr.Load(fileName);
            if (currentDescr.IsLoaded)
            {
                string asmPath = MySettings.AlgorithmsCachePath + "\\" + currentDescr.GetAlgorithmPath();
                string exePath = currentDescr.GetExePath();
                string exeShortName = exePath.Substring(exePath.LastIndexOf("\\") + 1);
                Assembly theAlgorithm = null;

                try
                {
                    theAlgorithm = Assembly.LoadFrom(asmPath);


                    var classTypes = from t in theAlgorithm.GetTypes()
                                     where t.IsClass && (t.GetInterface(typeof (IAlgorithm).FullName) != null)
                                     select t;

                    foreach (Type classType in classTypes)
                    {
                        string algorithmName = string.Empty;
                        KeyValuePair<string, IAlgorithm> value;

                        algorithmName = GetNameFromAtribute(classType);
                        IAlgorithm toAdd = (IAlgorithm) theAlgorithm.CreateInstance(classType.FullName, true,
                                                                                    BindingFlags.CreateInstance, null,
                                                                                    new object[]
                                                                                    {
                                                                                        exePath/*exeShortName*/,
                                                                                        currentDescr.GetAlgorithmName(),
                                                                                        asmPath,
                                                                                        fileName,
                                                                                        currentDescr.GetAlgorithmParameters()
                                                                                    },
                                                                                    null, new object[] {});

                        value = new KeyValuePair<string, IAlgorithm>(algorithmName, toAdd);
                        
                        ResContainer.AddUniveralAlgorithm(value);
                    }
                    //GetExe(exePath);
                }
                catch (Exception e)
                {
                    MessageBox.Show(fileName + " : содержит неверный формат. " + e.Message);
                    return;
                }
            }
        }

        /// <summary>
        /// Копирует исполняемый файл, необходимый для функционирования
        /// алгоритма исполнительного файла.
        /// </summary>
        /// <param name="exePath">Путь к исполняемому файлу.</param>
        private static void GetExe(string exePath)
        {
            if (File.Exists(exePath))
            {
                FileInfo f = new FileInfo(exePath);
                string appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
                File.Copy(exePath, Path.Combine(appDir, f.Name), true);
            }
            else
            {
               throw new Exception(Resources.ASMLOADER_ExeFileNotFount + exePath);
            }
        }

        /// <summary>
        /// Получает имя алгоритма исполнительного файла или адаптации параметров
        /// из атрибута библиотеки.
        /// </summary>
        /// <param name="t">Тип, описывающий алгоритм.</param>
        /// <returns>Имя алгоритма.</returns>
        private static string GetNameFromAtribute(Type t)
        {
            string toRet = "No name";
            var name = (from atr in t.GetCustomAttributes(false)
                       where atr.GetType() == typeof (AlgInfoAttribute)
                       select atr).FirstOrDefault();
            if (name is AlgInfoAttribute)
            {
                toRet = (name as AlgInfoAttribute).AlgListName; // mb need to be changed
            }

            return toRet;
        }

        /// <summary>
        /// Получает список параметров алгоритма адаптации параметров.
        /// </summary>
        /// <param name="t">Тип, описывающий алгоритм адаптации.</param>
        /// <returns>Список доступных параметров алгоритма адаптации.</returns>
        private static List<ParameterAttribute> GetAvailableParameters(Type t)
        {
            List<ParameterAttribute> toRet = new List<ParameterAttribute>();
            MemberInfo[] memberInfos = t.GetMembers();
            foreach (MemberInfo memberInfo in memberInfos)
            {
                ParameterAttribute tmp = ParseMethodOptionAtribute(memberInfo);
                if (tmp != null)
                {
                    toRet.Add(tmp);
                }
            }

            return (from parameterAttribute in toRet
                   orderby parameterAttribute.OrderNum
                   select parameterAttribute).ToList();
        }

        /// <summary>
        /// Извлекает данные о параметре алгоритма адаптации (такие как тип, граничные значения).
        /// </summary>
        /// <param name="info">Объект, описывающий параметр.</param>
        /// <returns>Объект, содержащий базовую информацию о параметре алгоритма адаптации.</returns>
        private static ParameterAttribute ParseMethodOptionAtribute(MemberInfo info)
        {
            ParameterAttribute toRet = null;
            var atr = (from attribute in info.GetCustomAttributes(false)
                       where attribute.GetType() == typeof (ParameterAttribute)
                       select attribute).FirstOrDefault();
            if (atr is ParameterAttribute)
            {
                toRet = atr as ParameterAttribute; // mb need to be changed
            }
            return toRet;
        }
    }
}
